'use strict'
const fs = require('fs')
const path = require('path')
const userHome = require('os').homedir()
const inquirer = require('inquirer')
const semver = require('semver')
const fse = require('fs-extra')
const glob = require('glob')
const ejs = require('ejs')
const Command = require('@kpzc-cli-dev/command')
const Package = require('@kpzc-cli-dev/package')
const { spinnerStart, sleep, execAsync } = require('@kpzc-cli-dev/utils')
const log = require('@kpzc-cli-dev/log')
const getProjectTemplate = require('./getProjectTemplate')

const TYPE_PROJECT = 'project'
const TYPE_COMPONENT = 'component'
const TEMPLATE_TYPE_NORMAL = 'normal'
const TEMPLATE_TYPE_CUSTOM = 'custom'
const WHITE_COMMAND = ['npm', 'cnpm']
const COMPONENT_FILE = '.componentrc'

class InitCommand extends Command {
  init() {
    this.projectName = this._argv[0] || ''
    //由于command版本问题 force => _cmd._optionValues.force
    // this.force = !!this._cmd._optionValues.force;
    //使用node多进程优化时候
    this.force = !!this._cmd.force
    log.verbose('projectName', this.projectName)
    log.verbose('force', this.force)
  }
  async exec() {
    try {
      //1. 准备阶段
      const projectInfo = await this.prepare()
      if (projectInfo) {
        //2. 下载模版
        this.projectInfo = projectInfo
        log.verbose('projectInfo', projectInfo)
        await this.downLoadTemplate()
        //3. 安装模版
        await this.installTemplate()
      }
    } catch (e) {
      log.error(e.message)
      if (process.env.LOG_LEVEL === 'verbose') {
        console.log(e)
      }
    }
  }
  //安装模版（标准模版和自定义）
  async installTemplate() {
    if (this.templateInfo) {
      if (!this.templateInfo.type) {
        this.templateInfo.type = TEMPLATE_TYPE_NORMAL
      }
      if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
        //标准模版安装
        await this.installTemplateNormal()
      } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
        //自定义模版安装
        await this.installTemplateCustom()
      } else {
        throw new Error('无法识别模版类型！')
      }
    } else {
      throw new Error('项目模版信息不存在！')
    }
  }
  //检查命令必须 npm cnpm
  checkCommand(cmd) {
    if (WHITE_COMMAND.includes(cmd)) {
      return cmd
    }
    return null
  }
  //node 子进程执行npm install 和 npm run dev
  async execCommand(command, errMsg) {
    let ret
    if (command) {
      const cmdArr = command.split(' ')
      const cmd = this.checkCommand(cmdArr[0])
      if (!cmd) {
        throw new Error('命令不存在！命令：' + command)
      }
      const args = cmdArr.slice(1)
      ret = await execAsync(cmd, args, {
        stdio: 'inherit',
        cwd: process.cwd()
      })
    }
    if (ret !== 0) {
      throw new Error(errMsg)
    }
    return ret
  }

  async ejsRender(options) {
    const dir = process.cwd()
    const projectInfo = this.projectInfo
    return new Promise((resolve, reject) => {
      glob(
        '**',
        {
          cwd: dir,
          ignore: options.ignore || '',
          nodir: true
        },
        (err, files) => {
          if (err) {
            reject(err)
          }
          Promise.all(
            files.map((file) => {
              const filePath = path.join(dir, file)
              return new Promise((resolve1, reject1) => {
                ejs.renderFile(filePath, projectInfo, {}, (err, result) => {
                  if (err) {
                    reject1(err)
                  } else {
                    //要重新写入才会渲染
                    fse.writeFileSync(filePath, result)
                    resolve1(result)
                  }
                })
              })
            })
          )
            .then(() => {
              resolve()
            })
            .catch((err) => {
              reject(err)
            })
        }
      )
    })
  }

  // 安装标准模版
  async installTemplateNormal() {
    log.verbose('templateNpm', this.templateNpm)
    //拷贝模版到当前目录
    let spinner = spinnerStart('正在安装模版...')
    await sleep()
    const targetPath = process.cwd()
    try {
      const templatePath = path.resolve(
        this.templateNpm.cacheFilePath,
        'template'
      )
      const targetPath = process.cwd()
      //确保路径存在，不存在创建目录
      fse.ensureDirSync(templatePath)
      fse.ensureDirSync(targetPath)
      fse.copySync(templatePath, targetPath)
    } catch (e) {
      throw e
    } finally {
      spinner.stop(true)
      log.success('模版安装成功')
    }
    //ejs 模版渲染
    const templateIgnore = this.templateInfo.ignore || []
    const ignore = ['**/node_modules/**', ...templateIgnore]
    await this.ejsRender({ ignore })
    // 如果说组件，生成组件配置信息
    await this.createComponentFile(targetPath)
    //安装依赖
    const { installCommand, startCommand } = this.templateInfo
    await this.execCommand(installCommand, '依赖安装失败！')
    //启动命令执行
    await this.execCommand(startCommand, '启动执行命令失败！')
  }
  async createComponentFile(targetPath) {
    const templateInfo = this.templateInfo
    const projectInfo = this.projectInfo
    if (templateInfo.tag.includes(TYPE_COMPONENT)) {
      const componentData = {
        ...projectInfo,
        buildPath: templateInfo.buildPath,
        examplePath: templateInfo.examplePath,
        npmName: templateInfo.npmName,
        npmVersion: templateInfo.version
      }
      const componentFile = path.resolve(targetPath, COMPONENT_FILE)
      fs.writeFileSync(componentFile, JSON.stringify(componentData))
    }
  }
  // 安装自定义模版
  async installTemplateCustom() {
    //查询入口文件是否存在
    if (await this.templateNpm.exists()) {
      const rootFile = this.templateNpm.getRootFilePath()
      if (fs.existsSync(rootFile)) {
        log.notice('开始执行自定义模版')
        const templatePath = path.resolve(
          this.templateNpm.cacheFilePath,
          'template'
        )
        const options = {
          templateInfo: this.templateInfo,
          projectInfo: this.projectInfo,
          sourcePath: templatePath,
          targetPath: process.cwd()
        }
        const code = `require('${rootFile}')(${JSON.stringify(options)})`
        log.verbose('code', code)
        await execAsync('node', ['-e', code], {
          stdio: 'inherit',
          cwd: process.cwd()
        })
        log.success('自定义模版安装成功')
      } else {
        throw new Error('自定义入口文件不存在')
      }
    }
  }
  //下载模版
  async downLoadTemplate() {
    //1. 通过项目模版API获取项目信息
    const { projectTemplate } = this.projectInfo
    const templateInfo = this.template.find(
      (item) => item.npmName === projectTemplate
    )
    const targetPath = path.resolve(userHome, '.kpzc-cli-dev', 'template')
    const storeDir = path.resolve(
      userHome,
      '.kpzc-cli-dev',
      'template',
      'node_modules'
    )
    const { npmName, version } = templateInfo
    this.templateInfo = templateInfo
    const templateNpm = new Package({
      targetPath,
      storeDir,
      packageName: npmName,
      packageVersion: version
    })
    if (!(await templateNpm.exists())) {
      //安装动画封装方法
      const spinner = spinnerStart('正在下载模版...')
      await sleep()
      try {
        await templateNpm.install()
      } catch (e) {
        throw e
      } finally {
        //停止动画
        spinner.stop(true)
        if (await templateNpm.exists()) {
          log.success('下载模版成功')
          this.templateNpm = templateNpm
        }
      }
    } else {
      const spinner = spinnerStart('正在更新模版...')
      await sleep()
      try {
        await templateNpm.update()
      } catch (e) {
        throw e
      } finally {
        spinner.stop(true)
        if (await templateNpm.exists()) {
          log.success('更新模版成功')
          this.templateNpm = templateNpm
        }
      }
    }
    //1.1 通过egg.js搭建后台系统
    //1.2 通过npm存储模版
    //1.3 将项目模版信息存储到mongodb数据库中
    //1.4 通过egg.js获取数据并且通过API返回
  }
  async prepare() {
    //0. 判断项目模版是否存在
    const template = await getProjectTemplate()
    if (!template || template.length === 0) {
      throw new Error('项目模版不存在！')
    }
    this.template = template
    //1. 判断当前目录是否为空
    const localPath = process.cwd()
    if (!this.ifDirEmpty(localPath)) {
      let ifContinue = false
      if (!this.force) {
        // 询问是否创建项目
        ifContinue = (
          await inquirer.prompt({
            type: 'confirm',
            name: 'ifContinue',
            default: false,
            message: '当前文件夹不为空， 是否继续创建项目?'
          })
        ).ifContinue
        if (!ifContinue) {
          return
        }
      }
      if (ifContinue || this.force) {
        //给用户做二次确认
        const { confirmDelete } = await inquirer.prompt({
          type: 'confirm',
          name: 'confirmDelete',
          default: false,
          message: '是否确认清空当前目录文件？'
        })
        if (confirmDelete) {
          //强制更新，清空当前目录
          fse.emptyDirSync(localPath)
        }
      }
    }
    return this.getProjectInfo()
  }
  async getProjectInfo() {
    function isValidName(v) {
      return /^(@[a-zA-Z0-9-_]+\/)?[a-zA-Z]+([-][a-zAZ][a-zA-Z0-9]*|[_][a-zAZ][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(
        v
      )
    }
    let projectInfo = {}
    let isProjectNameValid = false
    if (isValidName(this.projectName)) {
      isProjectNameValid = true
      projectInfo.projectName = this.projectName
    }
    //1. 选择创建项目还是组件

    const { type } = await inquirer.prompt({
      type: 'list',
      name: 'type',
      message: '请选择初始化类型',
      default: TYPE_PROJECT,
      choices: [
        {
          name: '项目',
          value: TYPE_PROJECT
        },
        {
          name: '组件',
          value: TYPE_COMPONENT
        }
      ]
    })
    //隔离出项目还是组件
    this.template = this.template.filter((template) =>
      template.tag.includes(type)
    )
    const title = type === TYPE_PROJECT ? '项目' : '组件'
    const projectNamePrompt = {
      type: 'input',
      name: 'projectName',
      message: `请输入${title}名称`,
      default: '',
      validate: function (v) {
        const done = this.async()
        setTimeout(function () {
          //1. 输入首字符必须英文字符
          //2. 尾字符必须英文或者数字,不能为字符
          //3. 字符仅允许"-_"
          if (!isValidName(v)) {
            done(`请输入合法${title}名称`)
            return
          }
          done(null, true)
        }, 0)
      },
      filter: function (v) {
        return v
      }
    }
    const projectPrompt = []

    if (!isProjectNameValid) {
      projectPrompt.push(projectNamePrompt)
    }
    projectPrompt.push(
      {
        type: 'input',
        name: 'projectVersion',
        message: `请输入${title}版本号`,
        default: '1.0.0',
        validate: function (v) {
          console.log(semver.valid(v))
          const done = this.async()
          setTimeout(function () {
            if (!!!semver.valid(v)) {
              done(`请输入${title}合法版本号`)
              return
            }
            done(null, true)
          }, 0)
          return
        },
        filter: function (v) {
          if (semver.valid(v)) {
            return semver.valid(v)
          } else {
            return v
          }
        }
      },
      {
        type: 'list',
        name: 'projectTemplate',
        message: `请选${title}择模版`,
        choices: this.createTemplateChoices()
      }
    )
    if (type === TYPE_PROJECT) {
      //2. 获取项目基本信息
      const project = await inquirer.prompt(projectPrompt)
      projectInfo = {
        ...projectInfo,
        type,
        ...project
      }
    } else if (type === TYPE_COMPONENT) {
      const descriptionPrompt = {
        type: 'input',
        name: 'componentDescription',
        message: '请输入组件描述信息',
        default: '',
        validate: function (v) {
          console.log(semver.valid(v))
          const done = this.async()
          setTimeout(function () {
            if (!v) {
              done(`请输入组件描述信息`)
              return
            }
            done(null, true)
          }, 0)
          return
        }
      }
      projectPrompt.push(descriptionPrompt)
      //3. 组件项目信息
      const component = await inquirer.prompt(projectPrompt)
      projectInfo = {
        ...projectInfo,
        type,
        ...component
      }
    }
    // 生成模版名称 className  用kebab-case库处理
    if (projectInfo.projectName) {
      projectInfo.name = projectInfo.projectName
      projectInfo.className = require('kebab-case')(
        projectInfo.projectName
      ).replace(/^-/, '')
    }
    if (projectInfo.projectVersion) {
      projectInfo.version = projectInfo.projectVersion
    }
    if (projectInfo.componentDescription) {
      projectInfo.description = projectInfo.componentDescription
    }
    return projectInfo
  }

  ifDirEmpty(localPath) {
    //fs.readdirSync 获取路径下是否存在文件 node_modules ,package.json
    let fileList = fs.readdirSync(localPath)
    fileList = fileList.filter(
      (file) => !file.startsWith('.') && ['node_modules'].indexOf(file) < 0
    )
    return !fileList || fileList.length <= 0
  }

  createTemplateChoices() {
    return this.template.map((item) => ({
      value: item.npmName,
      name: item.name
    }))
  }
}
function init(argv) {
  //参数 projectName, cmdObj,program
  //注意comannder v9.4.1 命令注册通过actions()第三个参数program， program.parent.opts().targetPath 获取targetPath
  // console.log('init', projectName,cmdObj.force,program.parent.opts().targetPath)
  // console.log('init', projectName,cmdObj.force,process.env.CLI_TARGET_PATH)
  return new InitCommand(argv)
}
module.exports = init
module.exports.InitCommand = InitCommand
